
import os
import sys
from PyQt4 import QtGui
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from . import _QDate2Str, _Str2Int, _LoadQIcon, _A2U, _U2A
from .UI.MainWindow import Ui_MainWindow
from .Person import Person
from .AddNewDialog import AddNewDialog
from .Notification import Notification

DATA_PATH, _ = os.path.split(sys.argv[0])

PERSONS_AUTOSAVE_NAME = r'%s\persons.txt' % DATA_PATH
NOTIFICATIONS_AUTOSAVE_NAME = r'%s\notifications.txt' % DATA_PATH

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.initIconsAndTray()

        self.notifyAt = 12

        self.persons = []
        self.loadPersons(PERSONS_AUTOSAVE_NAME)

        self.notifications = {}
        self.loadNotifications(NOTIFICATIONS_AUTOSAVE_NAME)

        self.ui.birthdayList.setColumnWidth(0, 400)
        self.ui.birthdayList.setColumnWidth(1, 200)

        self.updateBirthdayTree()
        self.updateStreamFilter()
        self.updateStream()
        self.updateCalendarFilter()
        self.updateCalendar()
        self.updateNotifications()

        self.startTimer(1000)
        self.lastShownTime = QDateTime.currentDateTime()

        self.processAutorun()

    def processAutorun(self):
        if '-hide' not in sys.argv:
            self.show()

        settings = QSettings(r"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run", QSettings.NativeFormat)
        mainPath = sys.modules["__main__"].__file__
        settings.setValue("DateNotifier", "%s %s -hide" % (sys.executable, mainPath))

    #
    # Callbacks
    #
    def addBirthday(self, person = None, update = False):
        if person is None:
            dlg = AddNewDialog(self)

            if not dlg.exec_():
                return

            person = dlg.getPerson()
            update = True

        self.persons.append(person)

        if update:
            self.updateBirthdayTree()
            self.autosave()

    def editBirthday(self):
        item = self.ui.birthdayList.currentItem()
        if item is None or not hasattr(item, 'person'):
            return

        dlg = AddNewDialog(self, item.person)
        if not dlg.exec_():
            return

        index = self.findPersonIndex(item.person)
        if index is not None:
            self.persons[index] = dlg.getPerson()
            self.updateBirthdayTree()
            self.autosave()

    def removeBirthday(self):
        item = self.ui.birthdayList.currentItem()
        if item is None or not hasattr(item, 'person'):
            return

        r = QMessageBox.warning(self, self.tr(u"Are you sure?"),
                                 self.tr(u'You are about to delete person: "%1".\nAre you sure?').arg(item.person.name),
                                 QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)

        if r != QtGui.QMessageBox.Yes:
            return

        index = self.findPersonIndex(item.person)
        if index is not None:
            del self.persons[index]
            self.updateBirthdayTree()
            self.autosave()

    def onTabChanged(self, index):
        if self.ui.tabWidget.currentWidget() is self.ui.calendarTab:
            self.updateCalendarFilter()
            self.updateCalendar()
        elif self.ui.tabWidget.currentWidget() is self.ui.streamTab:
            self.updateStreamFilter()
            self.updateStream()
        elif self.ui.tabWidget.currentWidget() is self.ui.notificationsTab:
            self.updateNotifications()
        elif self.ui.tabWidget.currentWidget() is self.ui.editTab:
            self.updateBirthdayTree()

    def onCalendarFilterChanged(self, filter):
        self.updateCalendar()

    def onStreamFilterChanged(self, filter):
        self.updateStream()

    def onNotifyAtChanged(self, value):
        self.notifyAt = _Str2Int(value)
        if not self.isLoading:
            self.autosave()

    #
    # UI related methods
    #
    _MONTHS = [
        "Jan", "Feb", "Mrt", "Apr",
        "May", "Jun", "Jul", "Aug",
        "Sep", "Oct", "Nov", "Dec"
    ]

    _TODAY_FONT = QFont("Comic Sans MS", 14)
    _CALENDAR_ROW_HEIGHT = 42

    _STREAM_FONT  = QFont("Comic Sans MS", 16)
    _STREAM_ROW_HEIGHT = 64

    _ALL_LABEL = "*All*"

    _STREAMS = [ "Left", "Date", "Name" ]

    _MIN_COLUMN_WIDTH = 100

    def findPersonIndex(self, person):
        for index, p in enumerate(self.persons):
            if p is person:
                return index
        return None

    def updateStreamFilter(self):
        self.ui.streamFilter.clear()
        self.ui.streamFilter.addItem(u"")

        for t in self.getTagList():
            icon = self.loadGroupIcon(t)
            if icon is not None:
                self.ui.streamFilter.addItem(icon, t)
            else:
                self.ui.streamFilter.addItem(t)

    def updateCalendarFilter(self):
        self.ui.calendarFilter.clear()
        self.ui.calendarFilter.addItem(u"")

        for t in self.getTagList():
            icon = self.loadGroupIcon(t)
            if icon is not None:
                self.ui.calendarFilter.addItem(icon, t)
            else:
                self.ui.calendarFilter.addItem(t)

    def updateStream(self):
        filter = _A2U(self.ui.streamFilter.currentText())

        self.ui.stream.clear()
        self.ui.stream.setHorizontalHeaderLabels(self._STREAMS)

        today = QDate().currentDate()

        stream = []
        for p in self.persons:
            if not filter or filter in p.tags:
                date = p.getQDate()
                if date.month() > today.month() or date.month() == today.month() and date.day() >= today.day():
                    date = QDate(today.year(), date.month(), date.day())
                else:
                    date = QDate(today.year() + 1, date.month(), date.day())

                stream.append((date, p))

        stream.sort(key = lambda x: x[0])

        self.ui.stream.setRowCount(len(stream))

        for index, (date, person) in enumerate(stream):
            left = today.daysTo(date)
            color = None

            if left > today.daysInMonth():
                sub = date.month() - today.month()
                sub = (sub if sub > 0 else sub + 12)
                left = "  %d m  " % sub
            else:
                color = QColor('Green') if left > 3 else QColor('Red')
                left = "  %d d  " % left

            font = self._STREAM_FONT

            def makeItem(itemIndex, text):
                item = QTableWidgetItem(text)
                item.setFont(font)
                if color is not None:
                    item.setTextColor(color)
                self.ui.stream.setItem(index, itemIndex, item)

            makeItem(0, left)
            makeItem(1, "   %02d.%02d   " % (person.getQDate().day(), person.getQDate().month()))
            makeItem(2, u"   %s   " % _A2U(person.name))

            self.ui.stream.setRowHeight(index, self._STREAM_ROW_HEIGHT)

        self.ui.stream.resizeColumnsToContents()

    def updateCalendar(self):
        filter = _A2U(self.ui.calendarFilter.currentText())

        self.ui.calendar.clear()
        self.ui.calendar.setSelectionBehavior(QAbstractItemView.SelectRows)

        year = [[] for _ in range(12)]
        for p in self.persons:
            if len(filter) == 0 or filter in p.tags:
                year[p.getQDate().month() - 1].append(p)

        today = QDate().currentDate()
        todayPerson = Person("%d" % today.day(), _QDate2Str(today), "")
        year[today.month() - 1].append(todayPerson)

        columnsCount = max(len(p) for p in year)
        self.ui.calendar.setColumnCount(columnsCount)
        self.ui.calendar.setHorizontalHeaderLabels([' ' for _ in range(columnsCount)])

        self.ui.calendar.setRowCount(12)
        self.ui.calendar.setVerticalHeaderLabels(self._MONTHS)

        for row, arr in enumerate(year):
            arr.sort(key=lambda x: x.getQDate().day())
            for column, p in enumerate(arr):
                item = QTableWidgetItem()
                if arr[column] is todayPerson:
                    item.setFont(self._TODAY_FONT)
                    item.setBackgroundColor(QColor("Tomato"))
                    item.setTextColor(QColor("White"))
                    item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                    item.setText(arr[column].name)
                else:
                    item.setText(u" %02d   %s  " % (arr[column].getQDate().day(), _A2U(arr[column].name)))

                self.ui.calendar.setItem(row, column, item)

            self.ui.calendar.setRowHeight(row, self._CALENDAR_ROW_HEIGHT)

        self.ui.calendar.resizeColumnsToContents()
        for index in range(self.ui.calendar.columnCount()):
            width = self.ui.calendar.columnWidth(index)
            if width < self._MIN_COLUMN_WIDTH:
                self.ui.calendar.setColumnWidth(index, self._MIN_COLUMN_WIDTH)

    def updateBirthdayTree(self):
        # store previously expanded elements
        expandedItems = set()
        for index in range(self.ui.birthdayList.topLevelItemCount()):
            item = self.ui.birthdayList.topLevelItem(index)
            if item.isExpanded():
                expandedItems.add(item.text(0))

        self.ui.birthdayList.clear()

        tags = self.getTagList()

        tagIndex = {}
        for t in tags:
            item = QTreeWidgetItem([_A2U(t)])
            tagIndex[t] = item
            self.updateGroupItemIcon(item, t)
            self.ui.birthdayList.addTopLevelItem(item)

        all = QTreeWidgetItem([self._ALL_LABEL])
        self.ui.birthdayList.addTopLevelItem(all)

        for p in sorted(self.persons, key=lambda x: x.name):
            info = [
                _A2U(p.name),
                p.date,
                u', '.join(_A2U(p) for p in sorted(p.tags))]
            item = QTreeWidgetItem(info)
            item.person = p
            all.addChild(item)
            for t in p.tags:
                item = QTreeWidgetItem(info)
                item.person = p
                tagIndex[t].addChild(item)

        # expand items that were previously expanded
        for index in range(self.ui.birthdayList.topLevelItemCount()):
            item = self.ui.birthdayList.topLevelItem(index)
            if item.text(0) in expandedItems:
                self.ui.birthdayList.expand(self.ui.birthdayList.indexFromItem(item))

    def updateNotifications(self):
        tags = self.getTagList()

        old = self.notifications
        self.notifications = {}
        for t, p in old.items():
            self.ui.notifications.removeWidget(p)
            if t in tags:
                self.notifications[t] = p

        for t in self.getTagList():
            if t not in self.notifications:
                self.notifications[t] = Notification(self, t)

        w = list(self.notifications.values())
        w.sort()
        for p in w:
            self.ui.notifications.addWidget(p)

    #
    # Misc methods
    #
    def initIconsAndTray(self):
        self.tray = QSystemTrayIcon(self)
        self.tray.setIcon(self.windowIcon())
        self.tray.setVisible(True)
        self.connect(self.tray, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.onTrayIcon)

        self.trayMenu = QMenu(self)
        self.trayMenu.addAction(self.ui.actionShow)
        self.trayMenu.addAction(self.ui.actionHide)
        self.trayMenu.addSeparator()
        self.trayMenu.addAction(self.ui.actionExit)
        self.tray.setContextMenu(self.trayMenu)

        self.ui.actionShow.setVisible(False)

    def onTrayIcon(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            if self.isHidden():
                self.showNormal()
            else:
                self.hide()

    def event(self, e):
        if not QEvent:
            return QMainWindow.event(self, e)

        if e.type() == QEvent.Hide:
            self.ui.actionShow.setVisible(True)
            self.ui.actionHide.setVisible(False)
        elif e.type() == QEvent.Show:
            self.ui.actionShow.setVisible(False)
            self.ui.actionHide.setVisible(True)

            self.updateStream()
            self.updateCalendar()

            self.lastShownTime = QDateTime.currentDateTime()

        return QMainWindow.event(self, e)

    def closeEvent(self, e):
        if self.tray.isVisible():
            self.hide()
            e.ignore()

    def quit(self):
        qApp.quit()

    def timerEvent(self, e):
        timeNow = QTime.currentTime()

        lastDate = self.lastShownTime.date()
        lastHour = self.lastShownTime.time().hour()

        if timeNow.hour() != self.notifyAt or lastDate == QDate.currentDate() and lastHour == timeNow.hour():
            return

        daysInTheYear = 365 if not QDate.currentDate().isLeapYear(QDate.currentDate().year()) else 366
        today = QDate.currentDate().dayOfYear()

        toNotify = set()

        for p in self.persons:
            date = p.getQDate().dayOfYear()
            if date < today:
                date += daysInTheYear

            rest = date - today

            for tag, nf in self.notifications.items():
                if tag not in p.tags or not nf.params.enabled:
                    continue

                if rest <= nf.params.alwaysIn or rest == 0:
                    toNotify.add(p)
                    break

                if rest <= nf.params.startIn and rest % nf.params.notifyEach == 0:
                    toNotify.add(p)
                    break

        if len(toNotify):
            self.showNormal()

    def getTagList(self):
        res = set()
        for p in self.persons:
            res.update(p.tags)
        res = list(res)
        res.sort()
        return res

    def loadGroupIcon(self, tag):
        if tag in self.notifications:
            icon = self.notifications[tag].params.groupIcon
            if icon:
                return _LoadQIcon(icon)

        return None

    def updateGroupItemIcon(self, item, tag):
        icon = self.loadGroupIcon(tag)
        if icon is not None:
            item.setIcon(0, icon)

    #
    # Save/load methods
    #
    def autosave(self):
        self.savePersons(PERSONS_AUTOSAVE_NAME)
        self.saveNotifications(NOTIFICATIONS_AUTOSAVE_NAME)

    def savePersons(self, filename):
        with open(filename, 'w') as f:
            for p in self.persons:
                f.write("%s # %s # %s\n" % (
                    _U2A(p.name),
                    _U2A(p.date),
                    ', '.join(_U2A(gr) for gr in sorted(p.tags))))

    def loadPersons(self, filename):
        self.persons = []
        self.isLoading = True

        try:
            f = open(filename, 'r')
            for line in f.readlines():
                line = _A2U(line)
                line = [p.strip() for p in line.split(u'#')]
                if len(line) >= 3:
                    self.persons.append(Person(line[0], line[1], line[2]))
            f.close()
        except IOError:
            pass
        finally:
            self.isLoading = False

    def saveNotifications(self, filename):
        backup = filename + '.bak'
        if os.access(backup, os.F_OK):
            os.remove(backup)
        if os.access(filename, os.F_OK):
            os.rename(filename, backup)

        try:
            with open(filename, 'w') as f:
                f.write('notifyAt # %d\n' % self.notifyAt)

                for p in self.notifications.values():
                    f.write('%s # %d # %d # %s # %d # %d\n' % (
                        _U2A(p.tag),
                        p.params.startIn,
                        p.params.notifyEach,
                        _U2A(p.params.groupIcon),
                        p.params.enabled,
                        p.params.alwaysIn))
        except:
            if os.access(filename, os.F_OK):
                os.remove(filename)
            if os.access(backup, os.F_OK):
                os.rename(backup, filename)
            raise

    def loadNotifications(self, filename):
        self.notifications = {}
        self.isLoading = True

        try:
            f = open(filename, 'r')
            for line in f.readlines():
                line = _A2U(line)
                line = [p.strip() for p in line.split(u'#')]

                if len(line) == 2 and line[0] == u'notifyAt':
                    self.notifyAt = _Str2Int(line[1])
                    self.ui.notifyAt.setText(str(self.notifyAt))

                if len(line) >= 3:
                    tag = line[0]
                    params = Notification.Params()
                    params.enabled = True
                    params.startIn = _Str2Int(line[1])
                    params.notifyEach = _Str2Int(line[2])

                    if len(line) >= 4:
                        params.groupIcon = line[3]

                    if len(line) >= 5:
                        params.enabled = bool(int(line[4]))

                    if len(line) >= 6:
                        params.alwaysIn = _Str2Int(line[5])

                    self.notifications[tag] = Notification(self, tag, params)
        except IOError:
            pass
        finally:
            self.isLoading = False
